package com.rainyun.rc.orm;

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.google.gson.Gson;
import com.rainyun.rc.ioc.Beanzy;
import com.rainyun.rc.orm.annotation.FieldInfo;
import com.rainyun.rc.orm.annotation.JsonField;
import com.rainyun.rc.orm.annotation.ReferData;
import com.rainyun.rc.orm.annotation.ReferField;
import com.rainyun.rc.util.RainStr;
import com.rainyun.rc.util.RainStr.StrEntry;

public abstract class CommonSql implements RainSql{

	protected Class<?> cur_clazz;
	protected ClazzInfo cur_info;
	protected CommonDB cur_db;
	/**sql语句内容*/
	public Map<SqlTypes,SqlPart> SqlParts = new LinkedHashMap<>();
	protected List<Class<?>> refers = new ArrayList<>();
	
	public enum SqlTypes{
		update,delete,insert,
		select,distinct,from,join,where,group,having,order,limit,
		extra,
	}

	/**获得Gson对象*/
	public Gson getGson(){
		return new Gson();
	}

	public CommonSql(Class<?> clazz, CommonDB db) {
		this.cur_clazz = clazz;
		this.cur_info = db.getClazzInfo(cur_clazz);
		this.cur_db = db;
	}

	/**返回当前实体的储存路径*/
	public String getPath() {
		return cur_db.getClazzInfo(this.cur_clazz).getPath();
	}

	@Override
	public<T> List<T> get() throws SQLException {
		List<Map<String, Object>> re = doQuery();
		List<T> list = new ArrayList<T>();
		if(re!=null) {
			//实体类和他的字段们<实体类,map<colname,字段>>
			Map<Class<?>,Map<String,Field>> obj_fields = new HashMap<Class<?>, Map<String,Field>>();
			//涉及的关联数据接收字段<所在类,字段list>
			Map<Class<?>,List<Field>> referdatas = new HashMap<Class<?>, List<Field>>(); 
			for(Map<String, Object> map:re) {
				if(obj_fields.isEmpty()) {
					for (Entry<String, Object> en:map.entrySet()) {
						String col = en.getKey();
						StrEntry stren = RainStr.toEntry(col,".");
						if(stren==null)continue;
						ClazzInfo ci = cur_db.getClazzInfo(stren.getKey());
						Field f = ci.getFields().get(stren.getValue());
						if(f==null)continue;
						Map<String, Field> fmap = obj_fields.get(ci.getClazz());
						if(fmap==null) {
							fmap = new HashMap<String, Field>();
							obj_fields.put(ci.getClazz(),fmap);
							
							//ReferData处理
							List<Field> dlist = new ArrayList<Field>();
							referdatas.put(ci.getClazz(),dlist);
							for(String dk:ci.getReferDataFields()) {
								Field df = ci.getFields().get(dk);
								dlist.add(df);
							}
						}
						fmap.put(col,f);
					}
				}
				
				//所有涉及的关联实体的数据集
				Map<Class<?>,Object> datas = new HashMap<Class<?>, Object>();
				try {
					for(Entry<Class<?>, Map<String, Field>> en:obj_fields.entrySet()) {
						Object tobj = en.getKey().newInstance();
						datas.put(en.getKey(),tobj);
						for(Entry<String, Field> en2:en.getValue().entrySet()) {
							Object r = map.get(en2.getKey());//获取字段值
							Field f = en2.getValue();
							Gson gson = getGson();
							if(gson!=null)
							if(f.isAnnotationPresent(JsonField.class)) {
								r = gson.fromJson((String) r,f.getType());
							}
							try {
								f.set(tobj, r);
							} catch (Exception e) {cur_db.handleError(getPath(),e);}
							//类型不匹配,输出异常
						}
					}
					for(Entry<Class<?>,Object> en:datas.entrySet()) {
						//本对象所有的referdata
						List<Field> tof = referdatas.get(en.getKey());
						if(tof!=null)
						for(Field f:tof) {
							ReferData fa = f.getAnnotation(ReferData.class);
							f.set(en.getValue(),datas.get(fa.value()));
						}
					}
				}catch (Exception e) {
					cur_db.handleError(getPath(), e);
				}
				
				//主对象
				T obj = (T) datas.get(cur_clazz);
				list.add(obj);
			}
		}
		return list;
	}

	
	/**利用现有参数执行查询语句
	 * @throws SQLException */
	public List<Map<String, Object>> doQuery() throws SQLException {
		StringBuffer sql = new StringBuffer();
		List<Object> args = new ArrayList<Object>();
		
		if(cur_info!=null) {
			if(!SqlParts.containsKey(SqlTypes.select)&&!SqlParts.containsKey(SqlTypes.join)){

				SqlPart joinpart = new SqlPart("");
				List<String> toselect = new ArrayList<String>();
				//所有涉及类
				List<Class<?>> trefers = new ArrayList<Class<?>>();
				trefers.add(cur_clazz);//加入自身
				for(Class<?> refc:refers)trefers.add(refc);//加入其它涉及类
				for(Class<?> refc:trefers) {
					//添加连表字段
					ClazzInfo info = cur_db.getClazzInfo(refc);
					for(String refk:info.getReferKeys()) {
						Field reff = info.getFields().get(refk);
						ReferField reffa = reff.getAnnotation(ReferField.class);
						if(!refers.contains(reffa.to()))continue;
						ClazzInfo tarinfo = cur_db.getClazzInfo(reffa.to());

						joinpart.append("LEFT JOIN "+tarinfo.getPath()+
								" ON "+info.getPath()+"."+refk+"="+
								tarinfo.getPath()+"."+
								(reffa.field().isEmpty()?tarinfo.getPrimary():reffa.field())+
								" ");
					}

					for(Entry<String, Field> en:info.getFields().entrySet()) { //添加select
						if(!en.getValue().isAnnotationPresent(ReferData.class))
							toselect.add(info.getPath()+"."+en.getKey()+" AS '"+info.getPath()+"."+en.getKey()+"'");
					}
				}

				SqlParts.put(SqlTypes.select,new SqlPart("SELECT "+RainStr.join(toselect.toArray(new String[0]),",")+" "));
				SqlParts.put(SqlTypes.join,joinpart);
			}
			SqlParts.put(SqlTypes.from,new SqlPart("FROM "+getPath()+" "));
		}
		
		for(SqlTypes type:SqlTypes.values()) {
			SqlPart sqlp = SqlParts.get(type);
			if(sqlp!=null) {
				sql.append(sqlp.getSql());
				args.addAll(sqlp.getArgs());
			}
		}
		
		try {
			List<Map<String, Object>> result = cur_db.sqlQuery(sql.toString(),args.toArray());
			cur_db.recentError = null;
			return result;
		} catch (SQLException e) {
			cur_db.handleError(getPath(),e);
			throw e;
		}
	}
	
	/**利用现有参数执行增删改语句
	 * @throws SQLException */
	@Override
	public int doUpdate() throws SQLException {
		StringBuffer sql = new StringBuffer();
		List<Object> args = new ArrayList<Object>();
		for(SqlTypes type:SqlTypes.values()) {
			SqlPart sqlp = SqlParts.get(type);
			if(sqlp!=null) {
				sql.append(sqlp.getSql());
				args.addAll(sqlp.getArgs());
			}
		}
		try {
			int re = cur_db.sqlUpdate(sql.toString(),args.toArray());
			cur_db.recentError = null;
			return re;
		} catch (SQLException e) {
			cur_db.handleError(getPath(),e);
			throw e;
		}
	}

	@Override
	public<T> T first() throws SQLException {
		limit(0,1);
		List<T> list = get();
		return list.size()>0?list.get(0):null;
	}

	@Override
	public<T> T find(Object key) throws SQLException {
		where("@"+cur_info.getPrimary(),"=",key);
		return first();
	}

	@Override
	public List<Map<String, Object>> getResultAll() throws SQLException {
		return doQuery();
	}
	
	public List<Map<String, Object>> getResultFromSet(ResultSet re) throws SQLException{
		List<Map<String, Object>> lmap = new ArrayList<Map<String,Object>>();
		while (re.next()) {
			Map<String, Object> map = new LinkedHashMap<String,Object>();
			ResultSetMetaData meta = re.getMetaData();
			for (int i = 1; i <= meta.getColumnCount(); i++) {
				String cname = meta.getColumnLabel(i);
				map.put(cname,re.getObject(i));
			}
			lmap.add(map);
		}
		return lmap;
	}

	@Override
	public <V> List<V> getResultList() throws SQLException {
		List<V> list = new ArrayList<V>();
		List<Map<String, Object>> re = doQuery();
		if(re!=null) {
			for(Map<String, Object> o:re)
			for(Entry<String, Object> en:o.entrySet()) {
				list.add((V) en.getValue());
				break;
			}
		}
		return list;
	}

	@Override
	public <V> V getResult() throws SQLException {
		List<Map<String, Object>> re = doQuery();
		Object obj = null;
		if(re!=null) {
			for(Entry<String, Object> en:re.get(0).entrySet()) {
				obj = en.getValue();break;
			}
		}
		return (V) obj;
	}

	@Override
	public Map<String, ?> getResultMap() throws SQLException {
		List<Map<String, Object>> re = doQuery();
		if(re!=null)
			return re.get(0);
		return null;
	}

	@Override
	public int insert(Object obj) throws SQLException {
		List<String> cols = new ArrayList<>();
		List<Object> insert_args = new ArrayList<Object>();
		for (Entry<String, Field> en:cur_info.getFields().entrySet()) {
			String name = en.getKey();
			Field f = en.getValue();
			if(f.isAnnotationPresent(FieldInfo.class)) {
				FieldInfo af = f.getAnnotation(FieldInfo.class);
				if(af.auto_increase())
					continue;//排除自动递增
			}
			if(f.isAnnotationPresent(ReferData.class)) continue;
			try {
				Gson gson = getGson();
				if(gson!=null)
				if(f.isAnnotationPresent(JsonField.class)) {
					//判断如果带有json序列化注解,则采用json存储
					String json = gson.toJson(f.get(obj));
					cols.add(name);
					insert_args.add(json);
					continue;
				}
				cols.add(name);
				insert_args.add(f.get(obj));
			} catch (Exception e) {
				cur_db.handleError(getPath(),e);
			}
		}
		SqlParts.put(SqlTypes.insert,
				new SqlPart("INSERT INTO "+getPath()+" ("+String.join(",",cols) +
						") VALUES ("+String.join(",",Collections.nCopies(cols.size(),"?"))+") "
						,insert_args));
		return doUpdate();
	}
	
	@Override
	public RainSql update(String col_name, Object value) {
		SqlPart sqlp = SqlParts.get(SqlTypes.update);
		col_name=getCol(col_name);
		if(sqlp==null) {
			SqlParts.put(SqlTypes.update,new SqlPart("UPDATE "+getPath()+" SET "+col_name+"=? ",value));
		}else {
			sqlp.append(","+col_name+"=? ",value);
		}
		return this;
	}

	@Override
	public int delete() throws SQLException {
		SqlParts.put(SqlTypes.delete,new SqlPart("DELETE "));
		SqlParts.put(SqlTypes.from,new SqlPart("FROM "+getPath()+" "));
		return doUpdate();
	}
	
	@Override
	public boolean delete(Object key) throws SQLException {
		return where("@"+cur_info.getPrimary(),"=",key).delete()>0;
	}

	@Override
	public RainSql where(String col_name, String op, Object... val) {
		SqlPart sqlp = SqlParts.get(SqlTypes.where);
		col_name=getCol(col_name);
		Object value = val;
		String place = "? ";
		if(op.toUpperCase().equals("IN")) {
			place = "("+String.join(",",Collections.nCopies(val.length,"?"))+") ";
		}else {
			value = val[0];
		}
		if(sqlp==null) {
			SqlParts.put(SqlTypes.where,new SqlPart("WHERE "+col_name+op+place,value));
		}else {
			sqlp.append("and "+col_name+op+place,value);
		}
		return this;
	}

	@Override
	public RainSql orWhere(String col_name, String op, Object... val) {
		SqlPart sqlp = SqlParts.get(SqlTypes.where);
		col_name=getCol(col_name);
		Object value = val;
		String place = "? ";
		if(op.toUpperCase().equals("IN")) {
			place = "("+String.join(",",Collections.nCopies(val.length,"?"))+") ";
		}else {
			value = val[0];
		}
		if(sqlp==null) {
			SqlParts.put(SqlTypes.where,new SqlPart("WHERE "+col_name+op+place,value));
		}else {
			sqlp.append("or "+col_name+op+place,value);
		}
		return this;
	}

	@Override
	public RainSql limit(int offset, int length) {
		SqlParts.put(SqlTypes.limit,new SqlPart("LIMIT ?,? ",offset,length));
		return this;
	}

	@Override
	public RainSql orderBy(String col,boolean isDesc) {
		SqlPart sqlp = SqlParts.get(SqlTypes.order);
		col=getCol(col);
		if(sqlp==null) {
			SqlParts.put(SqlTypes.order,new SqlPart("ORDER BY "+col+(isDesc?" DESC ":" ")));
		}else {
			sqlp.append(","+col+(isDesc?" DESC ":" "));
		}
		return this;
	}

	@Override
	public RainSql toPage(int page, int length) {
		return limit((page-1)*length, length);
	}

	@Override
	public RainSql join(String sql,Object... args) {
		sql+=" ";
		SqlPart sqlp = SqlParts.get(SqlTypes.join);
		if(sqlp==null) {
			SqlParts.put(SqlTypes.join,new SqlPart(sql,args));
		}else {
			sqlp.append(sql,args);
		}
		return this;
	}
		
	@Override
	public RainSql select(String... cols) {
		cols=getCols(cols);
		SqlParts.put(SqlTypes.select,new SqlPart("SELECT "+RainStr.join(cols,",")+" "));
		return this;
	}

	@Override
	public RainSql groupBy(String... cols) {
		cols=getCols(cols);
		SqlParts.put(SqlTypes.group,new SqlPart("GROUP BY "+RainStr.join(cols,",")+" "));
		return this;
	}
	
	@Override
	public RainSql refer(Class<?>... clazz) {
		refers = new ArrayList<>(Arrays.asList(clazz));
		return this;
	}

	@Override
	public int save(Object obj) throws SQLException {
		try {
			RainSql sql = cur_db.newSql(cur_clazz)
					.where("@"+cur_info.getPrimary(),"=",
							cur_info.getFields().get(cur_info.getPrimary()).get(obj));
			for(Entry<String, Field> en:cur_info.getFields().entrySet()) {
				sql.update(en.getKey(),en.getValue().get(obj));
			}
			int re = sql.doUpdate();
			if(re<=0) {
				re=insert(obj);
			}
			return re;
		} catch (SQLException e) {
			cur_db.handleError(getPath(),e);
			throw e;
		} catch (IllegalArgumentException|IllegalAccessException e) {
			cur_db.handleError(getPath(),e);
		}
		return 0;
	}

	protected String[] getCols(String ...cols){
		String[] newcols = new String[cols.length];
		int i=0;
		for (String str:cols){
			newcols[i]=getCol(str);
			i++;
		}
		return newcols;
	}
	protected String getCol(String col){
		String coln = RainStr.start(col,"@");
		if(coln!=null)col = getPath()+"."+coln;
		return col;
	}

}




